package network;

import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import core.Phrox;

public class ServerThread {

	public Peer root;
	protected Connection[] conList = new Connection[NetworkVars.MAX_CONNECTIONS];
	private int isAdding = 0;//so we create thread, and we don't remove it if we are in action of adding a connection
	
	
	public ServerThread(Peer p){
		root = p;
		System.out.println("NEW THREAD");
	}
	
	public void run(){
		while( (getEmpty() < conList.length || isAdding > 0 ) && !Phrox.exit ){
			//until we want to exit, or disconnect.
			
			for(int i=0; i<conList.length; i++){
				Connection c = conList[i];
				if( c == null || c.connectionStatus != 1){
					continue;
				}
				
				if( c.msgIn.ready() ){
					c.lastAlive = System.currentTimeMillis();
					
					Message m=null;
					try {
						m = NetIO.read(c);
					} catch (DisconnectionException e) {
						c.flagDisconnect=true;
					} catch (IOException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
					if( m != null){
						
						Message send = null;
						String tmp = null;
						
						switch(m.getType()){//TODO message in handling
						
						case Message.CONNECTION_MSG:
							if(m.getText().equals(NetworkingProtocol.DISCONNECT)){
								c.flagDisconnect=true;
								c.localDisconnect=false;
							}else{ 
								if(NetworkVars.watchData){
									System.out.println("Watching ConnectionMsg from "+c.user.get()+" w/ "+m.getText());
								}
								Phrox.outln("Bad connection msg: "+c.user.get());
							}
							break;
						case Message.INFO:
							//this has ip+port that can be connected to. as well as username
							
							try {
								if(m.getText().length() < NetworkVars.inetLen +2 && NetworkVars.watchData ){
									System.out.println("Watching Info from "+c.user.get()+" it's too short: "+
											m.getText()+"="+core.Util.getRepresentation(m.getText().getBytes()));
									break;
								}
								
								//get and parse the IP
								InetAddress a = InetAddress.getByAddress(m.getText().substring(0,NetworkVars.inetLen).getBytes());
								//Grab the next 2, and turn them into a port number.
								int port = (int) core.Util.add(m.getText().substring(NetworkVars.inetLen,NetworkVars.inetLen+2).getBytes());
								
								//TODO add link to 
							} catch (UnknownHostException e1) {
								// Address isn't formatted properly. ignore it
								System.out.println("Watching Info from "+c.user.get()+" bad ip: "+
										m.getText()+"="+core.Util.getRepresentation(m.getText().getBytes()));
					
							}
							
							break;
						case Message.KEEP_ALIVE:
							send = new Message(Message.KEEP_ALIVE,"");
							if(NetworkVars.watchData){
								System.out.println("Watching KeepAlive from "+c.user.get()+" w/ '"+m.getText()+"'");
							}
							break;
						case Message.PINGR:
							//us getting our ping back. or someone spoofing a ping message.
							try{
								long time = System.currentTimeMillis()-Long.parseLong(m.getText());
								Phrox.outln("Ping from "+ c.user.getName()+"/"+c.user.getIp()+" is "+time+" ms");
							} catch (NumberFormatException e){
								System.out.println("Spoofed or wrong PingR from: "+c.user.get());
								if(NetworkVars.watchData){
									System.out.println("Watching PingR from "+c.user.get()+" w/ "+m.getText());
								}
							}
							break;
						case Message.PINGS:
							try {
								Message l = new Message(Message.PINGR,m.getText());
								NetIO.write(c, l);//Send the ping back right away.
								if(NetworkVars.watchData){
									System.out.println("Watching PingS from "+c.user.get()+" w/ "+m.getText());
								}
							} catch (DisconnectionException e) {
								c.flagDisconnect=true;
							}
							break;
						case Message.REDUND:
							//TODO handle this message type
							break;
						case Message.S_REDUND:
							//TODO handle this message type
							break;
						case Message.TEXT_MSG:
							Phrox.outln(m.getText());
							root.send(m);
							break;
						default:
							if(NetworkVars.watchData){
								System.out.println("Watching def msg type("+m.getType()+") with data: "
										+core.Util.getString2(core.Util.getBytes(m.getText()))+" from "+c.user.get() );
								//print out text. make sure it doesn't have nulls.
							}
							break;
						}
						if(send != null){
							c.addMsg(send);
						}
					}//end: got a message
				}//end of reading message
				
				Message[] send = c.getMsgList();
				if( send.length > 0){
					for(Message m : send){
						try {
							NetIO.write(c, m);
						} catch (DisconnectionException e) {
							 //remove the connection if this happens.
							c.flagDisconnect=true;
						}
					}
				}//end message sending
				
			}
		}
		
		
	}
	
	
	/**
	 * 
	 * @param c Connection to add to list.
	 * @return returns an integer depending on what happens.
	 * 0: connection connected. 
	 * -1: couldn't connect. dropped.
	 * 3: can't hold another connection. Create new server thread
	 */
	public int addConnection(Connection c){
		
			isAdding++;
			if(isFull()){
				isAdding--;
				return 3;
			}
			
			synchronized (conList) {//make sure we don't accidentally lose a connection. Race condition.
				for (int i = 0; i < conList.length; i++) {//conList.length == NetworkVars.MAXCONNECTIONS
					//we connected, add this to the list of connections, at the first null
					if (conList[i] == null) {
						conList[i] = c;
						break;
					}
				}
			}
			isAdding--;
			c.localDisconnect = false;//assume we disconnect.
			join(c);
			if(isAdding < 0){
				//problem, not good.
				Thread.dumpStack();
			}
			return 0;//return connected
	}

	public void join(Connection c){
		Message m = new Message(Message.TEXT_MSG, NetworkVars.Msg_Format+c.user.getIp()+" has joined...");
		root.send(m);
	}
	
	public void leave(Connection c){
		Message m = new Message(Message.TEXT_MSG, NetworkVars.Msg_Format+c.user.getIp()+" has left...");
		root.send(m);
	}
	
	public boolean isFull(){
		return getEmpty() == 0;
	}
	
	/**
	 * Get the number of empty spaces.
	 * @return The number of possible connections that can be added.
	 */
	public int getEmpty(){
		int r = 0;
		for(Connection c : conList){
			if(c == null){
				r++;
			}
		}
		return r;
	}
	

	

}